﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Data;
using StyleMVVM.Data.Collections;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;
using StyleMVVM.ViewModel;
using StyleMVVM.Data;
#if !DOT_NET
using Windows.Foundation.Metadata;
#endif
#if NETFX_CORE
using Windows.UI.Popups;
using Windows.Foundation;
using Windows.UI.Xaml;
using Primitives = Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;
#else
using System.Windows;
using Primitives = System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Media;

#if WINDOWS_PHONE
using Windows.UI.Popups;

#endif
#endif

namespace StyleMVVM.View.Impl
{
	public sealed class UIVisualizationService : IUIVisualizationService, IDependencyInjectionContainerAware
	{
		private static readonly string supplemental = typeof(UIVisualizationService).FullName;
		private InternalWeakDictionary<string, FrameworkElement> registeredControl;
		private IViewBinderService binderService;

		public static void RegisterExport(IDependencyInjectionContainer container)
		{
			container.Register(typeof(UIVisualizationService)).As(typeof(IUIVisualizationService)).ImportMethod("ImportMethod");
		}

		public UIVisualizationService()
		{
			registeredControl = new InternalWeakDictionary<string, FrameworkElement>();
		}

		public IDependencyInjectionContainer Container { get; set; }

		public void ImportMethod(IViewBinderService binderService)
		{
			this.binderService = binderService;
		}

#if NETFX_CORE
		public IAsyncAction Popup(
#else
		public Task Popup(
#endif
			FrameworkElement control, string viewName)
		{
			return Popup(control, viewName, null, true, null, RelativePosition.Top | RelativePosition.Left, new Point());
		}

#if NETFX_CORE
		public IAsyncAction Popup(
#else
		public Task Popup(
#endif
			FrameworkElement control, string viewName, object navigationParameter)
		{
			return Popup(control,
			             viewName,
			             navigationParameter,
			             true,
			             null,
			             RelativePosition.Top | RelativePosition.Left,
			             new Point());
		}

#if NETFX_CORE
		public IAsyncAction Popup(
#else
		public Task Popup(
#endif
			FrameworkElement control,
			string viewName,
			object navigationParameter,
			bool isLightDismissEnabled,
			ICancellationToken cancellationToken,
			RelativePosition position,
			Point offset)
		{
			GeneralTransform transform = control.TransformToVisual(ViewHelper.MainContent);
			double xOffSet = offset.X;
			double yOffSet = offset.Y;

			if ((position & RelativePosition.Bottom) == RelativePosition.Bottom)
			{
				yOffSet += control.ActualHeight;
			}
			else if ((position & RelativePosition.Top) != RelativePosition.Top)
			{
				yOffSet += control.ActualHeight / 2;
			}

			if ((position & RelativePosition.Right) == RelativePosition.Right)
			{
				xOffSet += control.ActualWidth;
			}
			else if ((position & RelativePosition.Left) != RelativePosition.Left)
			{
				xOffSet += control.ActualWidth / 2;
			}

#if NETFX_CORE
			Point openPoint = transform.TransformPoint(new Point(xOffSet, yOffSet));
#else
			Point openPoint = transform.Transform(new Point(xOffSet, yOffSet));
#endif

			return Popup(openPoint, viewName, navigationParameter, isLightDismissEnabled, cancellationToken);
		}

#if NETFX_CORE
		[DefaultOverload]
		public IAsyncAction Popup(
#else
		public Task Popup(
#endif
			Point point, string viewName)
		{
			return Popup(point, viewName, null, true, null);
		}

#if NETFX_CORE
		[DefaultOverload]
		public IAsyncAction Popup(
#else
		public Task Popup(
#endif
			Point point, string viewName, object navigationParameter)
		{
			return Popup(point, viewName, navigationParameter, true, null);
		}

#if NETFX_CORE
		[DefaultOverload]
		public IAsyncAction Popup(Point point, string viewName, object navigationParameter,
		                          bool isLightDismissEnabled,
		                          ICancellationToken cancellationToken)
		{
			return InternalPopup(point, viewName, navigationParameter, isLightDismissEnabled, cancellationToken).AsAsyncAction();
		}
#else
		public Task Popup(Point point,
		                  string viewName,
		                  object navigationParameter,
		                  bool isLightDismissEnabled,
		                  ICancellationToken cancellationToken)
		{
			return InternalPopup(point, viewName, navigationParameter, isLightDismissEnabled, cancellationToken);
		}
#endif

		private Task InternalPopup(Point point,
		                           string viewName,
		                           object navigationParameter,
		                           bool isLightDismissEnabled,
		                           ICancellationToken cancellationToken)
		{
			TaskCompletionSource<object> returnValue = new TaskCompletionSource<object>();
			object newView = Container.Locate(viewName);

			if (newView != null)
			{
				binderService.Bind(newView, navigationParameter);

				Primitives.Popup newPopUp = new Primitives.Popup();

				newPopUp.Child = newView as UIElement;

				if (newPopUp.Child != null)
				{
					INavigationViewModel navigatingViewModel = null;

					newPopUp.HorizontalOffset = point.X;
					newPopUp.VerticalOffset = point.Y;

#if NETFX_CORE
					newPopUp.IsLightDismissEnabled = isLightDismissEnabled;
#elif DOT_NET
					newPopUp.StaysOpen = !isLightDismissEnabled;
#endif

					FrameworkElement child = newPopUp.Child as FrameworkElement;

					if (child != null)
					{
						newPopUp.DataContext = child.DataContext;
					}

					navigatingViewModel = newPopUp.DataContext as INavigationViewModel;

					INavigationParameterAwareViewModel awareViewModel = newPopUp.DataContext as INavigationParameterAwareViewModel;

					if (awareViewModel != null)
					{
						awareViewModel.NavigationParameter = navigationParameter;
					}

					IPopupViewModel popupViewModel = newPopUp.DataContext as IPopupViewModel;

					if (popupViewModel != null)
					{
						Binding newDataBinding = new Binding();

						newDataBinding.Source = popupViewModel;
						newDataBinding.Path = new PropertyPath("IsOpen");
						newDataBinding.Mode = BindingMode.TwoWay;

						newPopUp.SetBinding(Primitives.Popup.IsOpenProperty, newDataBinding);
					}

					if (cancellationToken != null)
					{
						cancellationToken.Cancelled += (x, y) => newPopUp.IsOpen = false;
					}

					newPopUp.Closed +=
						(sender, o) =>
							{
								try
								{
									if (navigatingViewModel != null)
									{
										navigatingViewModel.NavigatedFrom(newPopUp,
										                                  new StyleNavigationEventArgs());
									}
								}
								catch (Exception exp)
								{
									Logger.Error("Exception thrown while closing popup", supplemental, exp);
								}

								returnValue.TrySetResult(null);
							};

					if (navigatingViewModel != null)
					{
						try
						{
#if WINDOWS_PHONE
							navigatingViewModel.NavigatedTo(newPopUp, new StyleNavigationEventArgs());
#else
							navigatingViewModel.NavigatedTo(newPopUp,
							                                new StyleNavigationEventArgs { Parameter = navigationParameter });
#endif
						}
						catch (Exception exp)
						{
							Logger.Error("Exception thtown while calling navigatedTo", supplemental, exp);
						}
					}

					newPopUp.IsOpen = true;
				}
			}
			else
			{
				returnValue.TrySetException(new ArgumentException("Could not find exported object under the name: " + viewName));
			}

			return returnValue.Task;
		}

#if NETFX_CORE
		public Windows.Foundation.IAsyncOperation<string> PopupMenu(FrameworkElement control, params string[] menuOptions)
		{
			return InternalPopupMenu(control, RelativePosition.Center | RelativePosition.Top, default(Point), menuOptions).AsAsyncOperation();
		}

		public Windows.Foundation.IAsyncOperation<string> PopupMenu(FrameworkElement control, RelativePosition position, Point offset, params string[] menuOptions)
		{
			return InternalPopupMenu(control, position, offset, menuOptions).AsAsyncOperation();
		}

		[DefaultOverload]
		public Windows.Foundation.IAsyncOperation<string> PopupMenu(Point point, params string[] menuOptions)
		{
			return InternalPopupMenu(point, menuOptions).AsAsyncOperation();
		}
#else
		public Task<string> PopupMenu(FrameworkElement control, params string[] menuOptions)
		{
			return InternalPopupMenu(control, RelativePosition.Center | RelativePosition.Top, default(Point), menuOptions);
		}

		public Task<string> PopupMenu(FrameworkElement control,
		                              RelativePosition position,
		                              Point offset,
		                              params string[] menuOptions)
		{
			return InternalPopupMenu(control, position, offset, menuOptions);
		}

		[DefaultOverload]
		public Task<string> PopupMenu(Point point, params string[] menuOptions)
		{
			return InternalPopupMenu(point, menuOptions);
		}

#if DOT_NET

		public Task OpenWindow(string windowName,
		                       object navigationParameter = null,
		                       bool showDialog = false,
		                       bool owner = true,
		                       WindowStartupLocation? startupLocation = WindowStartupLocation.CenterOwner,
		                       Point? startupPoint = null)
		{
			TaskCompletionSource<object> completionSource = new TaskCompletionSource<object>();

			try
			{
				object exportedObject = Container.Locate(windowName);

				if (exportedObject != null)
				{
					Window newWindow = exportedObject as Window;

					if (newWindow != null)
					{
						binderService.Bind(newWindow, navigationParameter);

						if (owner && !newWindow.Equals(Application.Current.MainWindow))
						{
							newWindow.Owner = Application.Current.MainWindow;
						}

						if (startupLocation.HasValue)
						{
							newWindow.WindowStartupLocation = startupLocation.Value;
						}

						if (newWindow.WindowStartupLocation == WindowStartupLocation.Manual && startupPoint.HasValue)
						{
							newWindow.Left = startupPoint.Value.X;
							newWindow.Top = startupPoint.Value.Y;
						}

						newWindow.Closed += (sender, args) => completionSource.TrySetResult(true);

						SetWindowName(newWindow, windowName);

						IWindowViewModel windowViewModel = newWindow.DataContext as IWindowViewModel;

						if (windowViewModel != null)
						{
							windowViewModel.NavigationParameter = navigationParameter;

							try
							{
								windowViewModel.Opened();
							}
							catch (Exception exp)
							{
								Logger.Error("Exception thrown while calling Opened on type: " + windowViewModel.GetType().FullName,
								             supplemental,
								             exp);
							}
						}

						if (showDialog)
						{
							newWindow.ShowDialog();
						}
						else
						{
							newWindow.Show();
						}
					}
					else
					{
						throw new ArgumentException("Exported type does not inherit from window: " + exportedObject.GetType().FullName);
					}
				}
				else
				{
					throw new ArgumentException("No exported Window under the name: " + windowName);
				}
			}
			catch (Exception exp)
			{
				completionSource.TrySetException(exp);
			}

			return completionSource.Task;
		}

		public async Task<bool> CloseWindow(string windowName)
		{
			bool returnValue = false;

			foreach (Window window in Application.Current.Windows)
			{
				if (GetWindowName(window) == windowName)
				{
					window.Close();

					returnValue = true;
				}
			}

			return returnValue;
		}

		public static string GetWindowName(DependencyObject obj)
		{
			return (string)obj.GetValue(WindowNameProperty);
		}

		public static void SetWindowName(DependencyObject obj, string value)
		{
			obj.SetValue(WindowNameProperty, value);
		}

		// Using a DependencyProperty as the backing store for WindowName.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty WindowNameProperty =
			DependencyProperty.RegisterAttached("WindowName",
			                                    typeof(string),
			                                    typeof(UIVisualizationService),
			                                    new PropertyMetadata(null));
#endif

#endif

		private Task<string> InternalPopupMenu(FrameworkElement control, params string[] menuOptions)
		{
			return InternalPopupMenu(control,
			                         RelativePosition.Center,
			                         new Point(),
			                         menuOptions);
		}

		private Task<string> InternalPopupMenu(FrameworkElement control,
		                                       RelativePosition position = RelativePosition.Center,
		                                       Point offset = new Point(),
		                                       params string[] menuOptions)
		{
			GeneralTransform transform = control.TransformToVisual(ViewHelper.MainContent);
			double xOffSet = offset.X;
			double yOffSet = offset.Y;

			if ((position & RelativePosition.Bottom) == RelativePosition.Bottom)
			{
				yOffSet += control.ActualHeight;
			}
			else if ((position & RelativePosition.Top) != RelativePosition.Top)
			{
				yOffSet += control.ActualHeight / 2;
			}

			if ((position & RelativePosition.Right) == RelativePosition.Right)
			{
				xOffSet += control.ActualWidth;
			}
			else if ((position & RelativePosition.Left) != RelativePosition.Left)
			{
				xOffSet += control.ActualWidth / 2;
			}

#if NETFX_CORE
			Point openPoint = transform.TransformPoint(new Point(xOffSet, yOffSet));
#else
			Point openPoint = transform.Transform(new Point(xOffSet, yOffSet));
#endif

			return InternalPopupMenu(openPoint, menuOptions);
		}

		private async Task<string> InternalPopupMenu(Point point, params string[] menuOptions)
		{
#if NETFX_CORE
			PopupMenu menu = new PopupMenu();

			foreach (string menuOption in menuOptions)
			{
				menu.Commands.Add(new UICommand(menuOption));
			}

			IUICommand selectedCommand = await menu.ShowAsync(point);

			if (selectedCommand != null)
			{
				return selectedCommand.Label;
			}
#else

#endif
			return string.Empty;
		}
	}
}